package src

import (
	"container/list"
	"errors"
	"sync"
)

//链表节点信息
type CacheNode struct {
	Key, Value interface{}
}

func (cnode *CacheNode) NewCacheNode(k, v interface{}) *CacheNode {
	return &CacheNode{k, v}
}

//lru结构体
type LRUCache struct {
	Capacity int
	Nodelist *list.List
	//sync.Map
	//******go sync.map 自带所实现 支持多协程并发
	CacheMap map[interface{}]*list.Element
	//******可以拿出来 用分布式锁 redis etcd zk等 这样多进程和多协程都支持
	//锁的意义仅仅是多协程间的保护 也是保证链表和map数据一直的重要条件
	Mu *sync.Mutex
}

func NewLRUCache(cap int) *LRUCache {
	return &LRUCache{
		Capacity: cap,
		Nodelist: list.New(), //可以指定list长度 。。。为了实现方便是无限长度
		CacheMap: make(map[interface{}]*list.Element)}
}

//获取当前lru缓存大小
func (lru *LRUCache) Size() int {
	return lru.Nodelist.Len()
}

//写入一个元素
// 需要先判断
func (lru *LRUCache) Set(k, v interface{}) error {

	if lru.Nodelist == nil {
		return errors.New("LRUCache结构体未初始化.")
	}

	//获取锁
	lru.Mu.Lock()
	defer lru.Mu.Unlock()

	//能拿到说明缓存里面有 不用新申请新的node  直接写入就好
	if pElement, ok := lru.CacheMap[k]; ok {
		lru.Nodelist.MoveToFront(pElement)
		pElement.Value.(*CacheNode).Value = v
		return nil
	}

	//全新数据流程

	//申请一个node 直接放在最前面
	newElement := lru.Nodelist.PushFront(&CacheNode{k, v})
	lru.CacheMap[k] = newElement

	//容量超了 干掉一个
	if lru.Nodelist.Len() > lru.Capacity {
		//移掉最后一个
		lastElement := lru.Nodelist.Back()
		if lastElement == nil {
			return nil
		}
		cacheNode := lastElement.Value.(*CacheNode)
		delete(lru.CacheMap, cacheNode.Key)
		lru.Nodelist.Remove(lastElement)
	}
	return nil
}

//获取很简单  如果有就返回  并调整顺序 没有直接返回空
func (lru *LRUCache) Get(k interface{}) (v interface{}, ret bool, err error) {

	if lru.CacheMap == nil {
		return v, false, errors.New("LRUCache结构体未初始化.")
	}
	//获取锁
	lru.Mu.Lock()
	defer lru.Mu.Unlock()

	if pElement, ok := lru.CacheMap[k]; ok {
		lru.Nodelist.MoveToFront(pElement)
		return pElement.Value.(*CacheNode).Value, true, nil
	}
	return v, false, nil
}

func (lru *LRUCache) Remove(k interface{}) bool {

	if lru.CacheMap == nil {
		return false
	}

	if pElement, ok := lru.CacheMap[k]; ok {
		cacheNode := pElement.Value.(*CacheNode)
		delete(lru.CacheMap, cacheNode.Key)
		lru.Nodelist.Remove(pElement)
		return true
	}
	return false
}
